package org.hilo.boot.app.component;

import java.util.Date;
import java.util.Locale;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import org.hilo.boot.core.UT;
import org.hilo.boot.core.web.HighestOrderWebFilter;
import org.hilo.boot.core.web.HttpServletRequestHelper;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.context.annotation.Configuration;
import org.springframework.lang.Nullable;
import org.springframework.web.method.HandlerMethod;
import org.springframework.web.servlet.ModelAndView;
import org.springframework.web.servlet.handler.HandlerInterceptorAdapter;

/**
 * Gloabal Controller Interceptor
 * 
 * @see HandlerInterceptorAdapter
 * 
 *      <p>
 *      注意！！！！！！！！！ <br/>
 *      <p>
 *      没有使用 spring自带的RequestBodyAdvice接口，是因为RequestBodyAdvice只能拦截有@RequestBody的接口，局限性太大！！！
 *      <p>
 *      可以使用 spring自带的ResponseBodyAdvice<Object>接口，只要有@ResponseBody（RestController默认就是）都行，局限可以接受，但是要注意
 *      <p>
 *      ResponseBodyAdvice的beforeBodyWrite()只在controller正常return的情况下才会调用，如果出了Excetion则不会走到这里来。。。。
 *      <p>
 *      Exception会走到Exception的Handler（如果有），然后在DispatcherServlet的processDispatchResult方法内被render到自定义的error视图
 *      <p>
 *      如果没有被ExceptionHandler处理，则Spring内置默认的error拦截器会捕获，然后Spring会自动重定向（request.getDispatcherType()="ERROR"）到/error路由。
 *      <p>
 *      Spring默认的/error返回信息类似于：{timestamp=xxx, status=500, error=Internal Server Error, message=, path=/xxx} <br>
 *      <p>
 *      一种完整高效的API日志实现思路：（LogContext通过HttpServeltRequest的attributes透传上下文）
 *      <p>
 *      1、前置：通过本拦截器(HandlerInterceptorAdapter)捕获一手的HttpRequest请求信息。但此时还拿不到User信息。
 *      <p>
 *      2、后置（render前）-@ResponseBody/RestController类型的非异常拦截：通过ResponseBodyAdvice<Object>.beforeBodyWrite可以拿到一手的Result信息。此时有User。
 *      <p>
 *      3、后置（render前）-对于非@ResponseBody/RestController类型的非异常拦截：暂不处理，因为这可能是文件流、ResourceView、VelocityView、FreeMarkerView、UrlBasedView（JSP等）
 *      <p>
 *      4、后置（render前）-异常拦截：通过GlobalExceptionResolver捕获一手的Uncatched Exception。此时有User。
 *      <p>
 *      5、后置（render后）：都已经renderView后了，response已经没有意义了，但是有可能没有Render成功，此时还有Exception，反正url肯定没有正常返回的。此时记录CostTime盖棺定论、
 * 
 *      <p>
 *      不推荐使用 Aop的方式，例如：@Pointcut("execution(* com.pj..*Controller*.*(..))")，AOP是通过修改字节码来实现的，会形成AspectJAutoProxyBean
 *      ，而上面推荐的方式，只是借助Spring本身的Servlet扩展，非常高效。
 * 
 * @author zollty
 * @since 2018-2-23
 */
@Configuration("defaultControllerInterceptor")
@ConditionalOnProperty(name = "hilo.web.default-controller-interceptor", havingValue = "true", matchIfMissing = true)
public class GlobalControllerInterceptor extends HandlerInterceptorAdapter {
    @Value("${hilo.web.trace-request:}")
    private String[] traceRequest;
    private Logger reqLog;
    private Boolean trace;
    private boolean traceAll;
    @Value("${hilo.web.enable-i18n:false}")
    private boolean enableI18n;
    @Value("${hilo.web.trace-request-body:false}")
    private boolean traceBody;

    @Override
    public boolean preHandle(HttpServletRequest request, 
            HttpServletResponse response, Object handler)
            throws Exception {
        
        if (!(handler instanceof HandlerMethod)) {
            return true;
        }

        if (enableI18n) {
            /**
             * 设置locale，供临时使用，参见：{@link #GlobalResponseBodyAdvice}
             */
            Locale locale = request.getLocale();
            if (locale != null) {
                response.addHeader("locale", request.getLocale().toString());
            }
        }

        if (getTrace()) {
            if (reqLog == null) {
                reqLog = LoggerFactory.getLogger("reqLog");
            }
            if (reqLog.isDebugEnabled()) {
                if (traceAll) {
                    logReqLog(request);
                } else {
                    String uri = request.getRequestURI();
                    for (String prefix : traceRequest) {
                        if (uri.startsWith(prefix)) {
                            logReqLog(request);
                            break;
                        }
                    }
                }
            }
        }

        return true;
    }

    void logReqLog(HttpServletRequest request) {
        Object requestId = request.getAttribute(HighestOrderWebFilter.REQ_LOG_CONTEXT_ID);
        if (requestId != null) {
            secondLogBody(request);
        } else {
            firstLog(request);
        }
    }

    void firstLog(HttpServletRequest request) {
        String requestId = UT.Date.getUniqueDatePatternTimeMillisNoSplit();
        request.setAttribute(HighestOrderWebFilter.REQ_LOG_CONTEXT_ID, requestId);
        reqLog.debug(HttpServletRequestHelper.getRequestInfo(request, traceBody,
                true, false, requestId));
    }

    void secondLogBody(HttpServletRequest request) {
        if (traceBody || request.getContentLength() <= 0) {
            return;
        }
        StringBuilder sb = new StringBuilder(1024);
        sb.append("-----------------------").append(UT.Date.formatNowTime())
                .append("-------------------------------------\n");
        Object requestId = request.getAttribute(HighestOrderWebFilter.REQ_LOG_CONTEXT_ID);
        if (requestId != null) {
            sb.append("requestID  : ").append(requestId).append("\n");
        }
        sb.append("Body       : ").append(HttpServletRequestHelper.getBodyIfNecessary(request)).append("\n");
        reqLog.debug(sb.toString());
    }

    public Boolean getTrace() {
        if (trace == null) {
            if (traceRequest.length == 0) {
                trace = false;
            } else {
                trace = true;
                if (traceRequest[0].equals("all") || traceRequest[0].equals("api")) {
                    traceAll = true;
                }
            }
        }
        return trace;
    }

    /**
     * This implementation is empty.
     */
    @Override
    public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler,
            @Nullable ModelAndView modelAndView) throws Exception {
    }

    /**
     * This implementation is empty.
     */
    @Override
    public void afterCompletion(HttpServletRequest request, HttpServletResponse response,
            Object handler, @Nullable Exception ex) throws Exception {
    }

    /**
     * This implementation is empty.
     */
    @Override
    public void afterConcurrentHandlingStarted(HttpServletRequest request,
            HttpServletResponse response, Object handler) throws Exception {
    }

}
